#include <atlbase.h>
#include <DShow.h>

#include "control.h"
#include "scene.h"
#include "base.h"
#include "setting.h"
#include "kinetic.h"

using namespace Ogre;
class MouseCursor
{
private:
    Overlay* mGuiOverlay;
    OverlayContainer* mCursorContainer;
    TexturePtr mTexture;
    MaterialPtr mMaterial;
 
    Real mWindowWidth;
    Real mWindowHeight;
 
public:
    MouseCursor(): mGuiOverlay(0), mCursorContainer(0)
    {
        mMaterial = MaterialManager::getSingleton().create("MouseCursor/default", "General");
        mCursorContainer = (OverlayContainer*) OverlayManager::getSingletonPtr()->createOverlayElement("Panel", "MouseCursor");
        mCursorContainer->setMaterialName(mMaterial->getName());
        mCursorContainer->setPosition(0, 0);
		mCursorContainer->setDimensions(1, 1);
 
        mGuiOverlay = OverlayManager::getSingletonPtr()->create("MouseCursor");
        mGuiOverlay->setZOrder(649);
        mGuiOverlay->add2D(mCursorContainer);
        mGuiOverlay->show();
    }
 
    ~MouseCursor(void) {}
 
    void setImage(const String& filename)
    {
        mTexture = TextureManager::getSingleton().load(filename, "General");
 
        TextureUnitState *pTexState;
        if(mMaterial->getTechnique(0)->getPass(0)->getNumTextureUnitStates())
        {
            pTexState = mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0);
        }
        else
        {
            pTexState = mMaterial->getTechnique(0)->getPass(0)->createTextureUnitState( mTexture->getName() );
        }
        pTexState->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
        mMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA);
    }
 
    void setWindowDimensions(unsigned int width, unsigned int height) {
        mWindowWidth = (width > 0) ? width : 1;
        mWindowHeight = (height > 0) ? height : 1;
 
        mCursorContainer->setWidth(mTexture->getWidth() / mWindowWidth);
        mCursorContainer->setHeight(mTexture->getHeight() / mWindowHeight);
    }
 
    void setVisible(bool visible)
    {
        if(visible) {
            mCursorContainer->show();
        } else {
            mCursorContainer->hide();
        }
    }
 
    void updatePosition(int x, int y)
    {
        Real rx = x / mWindowWidth;
        Real ry = y / mWindowHeight;
		
		mCursorContainer->setPosition(clamp(rx, 0.0f, 1.0f), clamp(ry, 0.0f, 1.0f));
    }
 
    Real clamp(Real a, Real min, Real max) {
        if (a < min) {
            return min;
        }
        if (a > max) {
            return max;
        } 
        return a;
    }
};

bool Base::keyPressed(const OIS::KeyEvent &arg)
{
	//TODO
	return true;
}

bool Base::keyReleased( const OIS::KeyEvent &arg )
{
    return true;
}

bool Base::mouseMoved( const OIS::MouseEvent &arg )
{
    if (tray_manager->injectMouseMove(arg)) return true;
    return true;
}

bool Base::mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
{
    if (tray_manager->injectMouseDown(arg, id)) return true;
    return true;
}

bool Base::mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
{
    if (tray_manager->injectMouseUp(arg, id)) return true;
    return true;
}


Controler::Controler():
	parent(NULL), input_manager(NULL), mouse(NULL), keyboard(NULL), capture(NULL)
{
	kinetic = new Kinetic();
}
Controler::~Controler()
{
	if (capture)
		cvReleaseCapture(&capture);
}

void Base::windowClosed(Ogre::RenderWindow* rw)
{
	//MessageBox(NULL,"windowClosed","1",MB_OK);

	OIS::InputManager* im = controler.get_input_manager();
	if (rw == window && im != NULL)
	{
		im->destroyInputObject(controler.get_mouse());
		im->destroyInputObject(controler.get_keyboard());
		OIS::InputManager::destroyInputSystem(im);
		im = 0;
	}
}

void Base::windowResized(Ogre::RenderWindow* rw)
{
	//MessageBox(NULL,"windowResized","1",MB_OK);

	unsigned int width, height, depth;
	int left, top;
	rw->getMetrics(width, height, depth, left, top);

	const OIS::MouseState &m = controler.get_mouse()->getMouseState();
	m.width = width;
	m.height = height;
	//scene_mouse_resize(width, height);
}

int CameraCount()
{
	int count = 0;
	CoInitialize(NULL);

	// Enumerate all video capture devices
	CComPtr<ICreateDevEnum> pCreateDevEnum;
	HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum);

	CComPtr<IEnumMoniker> pEm;
	hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0);
	if (hr != NOERROR)
		return count;

	pEm->Reset();
	ULONG cFetched;
	IMoniker *pM;
	while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
		count++;
	pCreateDevEnum = NULL;
	pEm = NULL;
	return count;
}

int CameraName(int nCamID, char* sName, int nBufferSize)
{
	int count = 0;
	CoInitialize(NULL);

	// enumerate all video capture devices
	CComPtr<ICreateDevEnum> pCreateDevEnum;
	HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum);

	CComPtr<IEnumMoniker> pEm;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0);
	if (hr != NOERROR) return 0;

	pEm->Reset();
	ULONG cFetched;
	IMoniker *pM;
	while(hr = pEm->Next(1, &pM, &cFetched), hr == S_OK)
	{
		if (count == nCamID)
		{
			IPropertyBag *pBag=0;
			hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pBag);
			if(SUCCEEDED(hr))
			{
				VARIANT var;
				var.vt = VT_BSTR;
				hr = pBag->Read(L"FriendlyName", &var, NULL); //read more attributes, infos
				if(hr == NOERROR)
				{
			        //get name			
					WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, sName, nBufferSize, "", NULL);
					SysFreeString(var.bstrVal);				
		        }
			    pBag->Release();
			}
			pM->Release();
			break;
		}
		count++;
	}
	pCreateDevEnum = NULL;
	pEm = NULL;
	
	return 1;
}

void Controler::init(Base* par)
{
	parent = par;
	// create input devices mouse and keyboard.
	
	size_t windowHnd = 0;
	parent->get_window()->getCustomAttribute("WINDOW", &windowHnd);

	input_manager = OIS::InputManager::createInputSystem(windowHnd);

	keyboard = static_cast<OIS::Keyboard*>
		(input_manager->createInputObject(OIS::OISKeyboard, false));
	mouse = static_cast<OIS::Mouse*>
		(input_manager->createInputObject(OIS::OISMouse, true));
	
	if (keyboard == NULL)
		throw Ogre::Exception(2, parent->get_text("ERROR_KB"), "control.cpp, Controler::init");
	
	if (mouse == NULL)
		throw Ogre::Exception(2, parent->get_text("ERROR_MS"), "control.cpp, Controler::init");

	mouse->setEventCallback(parent);
    keyboard->setEventCallback(parent);
}

std::vector<Ogre::String> Controler::get_camera_info()
{
	int cam_id = CV_CAP_ANY, cam_tot = CameraCount();
	std::vector<Ogre::String> cams;
	for (int i = 0; i < cam_tot; ++i)
	{
		char s[101];
		CameraName(i, s, 100);
		cams.push_back(s);
	}
	return cams;
}

void Controler::capture_camera(int cam_id)
{
	if (capture)
		cvReleaseCapture(&capture);
	capture = cvCreateCameraCapture(cam_id);
}

void Controler::do_capture()
{
	mouse->capture();
	keyboard->capture();
}

OIS::InputManager* Controler::get_input_manager()
{
	return input_manager;
}

OIS::Mouse* Controler::get_mouse()
{
	return mouse;
}

OIS::Keyboard* Controler::get_keyboard()
{
	return keyboard;
}

void Base::buttonHit(OgreBites::Button* button)
{
	sounder.play_once("hit");
	Ogre::String name = button->getName();

	if (name == "start_select")
	{
		if (cfg_changed)
		{
			flag = BASE_PAUSE;
			last_flag = BASE_START;
			reinit();
		}
		else
			flag = BASE_START;
	}
	else if (name == "exit_select")
	{
		flag = BASE_PAUSE;
		tray_manager->showYesNoDialog(get_UTFtext("TEXT_EXITCAP"), get_UTFtext("TEXT_EXIT"));
	}
	else if (name == "apply_select")
	{
		if (cfg_changed)
		{
			flag = BASE_PAUSE;
			reinit();
		}
	}
	else if (name == "about_select")
	{
		flag = BASE_PAUSE;
		tray_manager->showOkDialog(get_UTFtext("SEL_ABT"), 
			 get_UTFtext("TEXT_CAP")+"\n\n"
			+get_UTFtext("TEXT_INFO")+"\n\n"
			+get_UTFtext("TEXT_DATE")+"\n\n"
			+get_UTFtext("TEXT_EXTRA1")+"\n\n"
			+get_UTFtext("TEXT_EXTRA2")+"\n\n");
	}
	else if (name == "help_select")
	{
		flag = BASE_HELP;
	}

}

void Base::itemSelected(OgreBites::SelectMenu* menu)
{
	//if (menu->getName() != "cam_select")
	cfg_changed = true;
	sounder.play_once("hit");
}

void Base::labelHit(OgreBites::Label* label)
{
	sounder.play_once("hit");
}

void Base::sliderMoved(OgreBites::Slider* slider)
{
	cfg_changed = true;
	//static clock_t buff = 0;
	// TODO
	if (slider->getName() == "music_select")
	{
		sounder.set_music_vol(slider->getValue() * 0.01f);
		
	}
	else if (slider->getName() == "fx_select")
	{
		sounder.set_fx_vol(slider->getValue() * 0.01f);
	}
}

void Base::checkBoxToggled(OgreBites::CheckBox* box)
{
	
}

void Base::okDialogClosed(const Ogre::DisplayString& message)
{
	sounder.play_once("hit");
	if (flag == BASE_PAUSE)
		flag = last_flag;
	else
		flag = BASE_MAINTAIN;
}

void Base::yesNoDialogClosed(const Ogre::DisplayString& question, bool yesHit)
{
	sounder.play_once("hit");
	if (question == get_UTFtext("TEXT_P"))
	{
		if (!yesHit)
		{
			flag = BASE_MAINTAIN;
		}
		else
		{
			flag = BASE_SELECT;
		}
	}
	else if (question == get_UTFtext("TEXT_EXIT"))
	{
		if (!yesHit)
		{
			flag = BASE_MAINTAIN;
		}
		else
		{
			flag = BASE_EXIT;
		}
	}
	else if (question == get_UTFtext("TEXT_CFG"))
	{
		/*
		MessageBox(NULL, std::to_string((long long)flag).c_str(), 
			std::to_string((long long)last_flag).c_str(), MB_OK);*/

		if (!yesHit)
		{
			CopyFile(CONFIG_BAKFILE_NAME, CONFIG_FILE_NAME, false);
			flag = last_flag;
		}
		else
		{
			flag = last_flag;
		}
	}
}
void Controler::set_value(Ogre::Real left_len, Ogre::Real right_len, Ogre::Real left_hei, Ogre::Real right_hei)
{
	left_height = left_hei;
	left_length = left_len;
	right_height = right_hei;
	right_length = right_len;
}


Kinetic* Controler::get_kinetic()
{
	return kinetic;
}

void Controler::drive(bool is_pro, bool is_cam, SceneNode* sn,
					  OIS::Keyboard* kb, Ogre::Real time, HANDLE lock)
{
	if (time > MAX_FRAME_TIME)
		time = MAX_FRAME_TIME;
	if (time < MIN_FRAME_TIME)
		time = MIN_FRAME_TIME;

	if (is_pro)
	{
		if (!is_cam)
		{
			float ar = Ogre::Math::Exp(-KBMODE_DEPRESS_RATE * time);
			left_height *= ar;
			right_height *= ar;
			left_length *= ar;
			right_length *= ar;

			if (kb->isKeyDown(OIS::KC_UP))
			{
				left_height -= KBMODE_INCREASE_RATE * time;
				right_height -= KBMODE_INCREASE_RATE * time;
			}
			if (kb->isKeyDown(OIS::KC_DOWN))
			{
				left_height += KBMODE_INCREASE_RATE * time;
				right_height += KBMODE_INCREASE_RATE * time;
			}
			if (kb->isKeyDown(OIS::KC_A))
			{
				left_height -= KBMODE_INCREASE_RATE * time;
				right_height += KBMODE_INCREASE_RATE * time;
			}
			if (kb->isKeyDown(OIS::KC_D))
			{
				left_height += KBMODE_INCREASE_RATE * time;
				right_height -= KBMODE_INCREASE_RATE * time;
			}
		
			if (kb->isKeyDown(OIS::KC_W))
			{
				left_length += KBMODE_INCREASE_RATE * time;
				right_length += KBMODE_INCREASE_RATE * time;
			}
			
			if (!kb->isKeyDown(OIS::KC_S))
			{
				left_length += KBMODE_INCREASE_RATE * time;
				right_length += KBMODE_INCREASE_RATE * time;
			}

			if (kb->isKeyDown(OIS::KC_LEFT))
			{
				right_length -= KBMODE_INCREASE_RATE * time;
			}
			
			if (kb->isKeyDown(OIS::KC_RIGHT))
			{
				left_length -= KBMODE_INCREASE_RATE * time;
			}
		}
	
		Vector3 tx, ty, tz, tg(0, -AIR_G, 0);
		sn->getOrientation().ToAxes(tx, ty, tz);
		
		/*
		cam_move_near(parent->get_cam(),
					  sn->getPosition() + (tx * CAM_OFFSET.x + ty * CAM_OFFSET.y + tz * CAM_OFFSET.z),
					  time, 7.f);
		*/
		
		WaitForSingleObject(lock, INFINITE);
		Real yp = (left_height + right_height)/2;
		Real xp = (right_height - left_height)/2;
		Real xl = (left_length - right_length)/2;
		Real yl = (left_length + right_length)/2;
		ReleaseMutex(lock);
		
		kinetic->force_work(Vector3(tx.dotProduct(tg), ty.dotProduct(tg), tz.dotProduct(tg)), time);
	
		kinetic->roll_work(yp, time);
		kinetic->pitch_work(xp, time);
		kinetic->yaw_work(xl, time);

		kinetic->affect(sn, time, yl * 2 * ACCELERATE_RATE);

		parent->get_cam()->setPosition(sn->getPosition() + 2 * tz * CAM_OFFSET.z);
		parent->get_cam()->setOrientation(sn->getOrientation());
		
	}
	else
	{
		if (!is_cam)
		{
			float ar = Ogre::Math::Exp(-KBMODE_DEPRESS_RATE * time);
			left_height *= ar;
			right_height *= ar;
			left_length *= ar;
			right_length *= ar;

			if (kb->isKeyDown(OIS::KC_UP))
			{
				left_height -= KBMODE_INCREASE_RATE * time;
				right_height -= KBMODE_INCREASE_RATE * time;
			}
			if (kb->isKeyDown(OIS::KC_DOWN))
			{
				left_height += KBMODE_INCREASE_RATE * time;
				right_height += KBMODE_INCREASE_RATE * time;
			}
			if (kb->isKeyDown(OIS::KC_A))
			{
				left_height -= KBMODE_INCREASE_RATE * time;
				right_height += KBMODE_INCREASE_RATE * time;
			}
			if (kb->isKeyDown(OIS::KC_D))
			{
				left_height += KBMODE_INCREASE_RATE * time;
				right_height -= KBMODE_INCREASE_RATE * time;
			}
		
			if (kb->isKeyDown(OIS::KC_W))
			{
				left_length += KBMODE_INCREASE_RATE * time;
				right_length += KBMODE_INCREASE_RATE * time;
			}
			
			if (!kb->isKeyDown(OIS::KC_S))
			{
				left_length += KBMODE_INCREASE_RATE * time;
				right_length += KBMODE_INCREASE_RATE * time;
			}
			
			if (kb->isKeyDown(OIS::KC_LEFT))
			{
				right_length -= KBMODE_INCREASE_RATE * time;
			}
			
			if (kb->isKeyDown(OIS::KC_RIGHT))
			{
				left_length -= KBMODE_INCREASE_RATE * time;
			}
		}
	
		Vector3 tx, ty, tz, tg(0, -AIR_G, 0);
		sn->getOrientation().ToAxes(tx, ty, tz);

		kinetic->force_work(Vector3(tx.dotProduct(tg), ty.dotProduct(tg), tz.dotProduct(tg)), time);
	
		WaitForSingleObject(lock, INFINITE);
		Real yp = (left_height + right_height)/2;
		Real xp = (right_height - left_height)/2;
		Real xl = (left_length - right_length)/2;
		Real yl = (left_length + right_length)/2;
		ReleaseMutex(lock);

		kinetic->yaw_work(xl, time);

		Real nowyp = Math::ASin(-tz.y).valueRadians() / Degree(135).valueRadians();

		kinetic->roll_work((yp - nowyp), time);
		
		Real nowxp = Math::ASin(tx.y).valueRadians() / Degree(135).valueRadians();
		kinetic->pitch_work((xp - nowxp) * 1.5, time);

		kinetic->affect(sn, time, yl * 2 * ACCELERATE_RATE);

		
		cam_move_near(parent->get_cam(),
					  sn->getPosition() + (tx * CAM_OFFSET.x + ty * CAM_OFFSET.y + tz * CAM_OFFSET.z),
					  time, 14.f);
		parent->get_cam()->lookAt(sn->getPosition());
	}


	Real p = kinetic->get_velocity().length(), p2 = p, p3 = p;
	if (p > LIFT_OFF_SPEED) p = LIFT_OFF_SPEED;
	if (p2 > LIFT_OFF_SPEED * 2) p2 = LIFT_OFF_SPEED * 2;
	if (p3 > LIFT_OFF_SPEED * 3) p3 = LIFT_OFF_SPEED * 3;

	p /= LIFT_OFF_SPEED;
	p += 1;

	p2 /= LIFT_OFF_SPEED;
	if (p2 < 1)
		p2 = 1;

	p3 /= LIFT_OFF_SPEED;
	if (p3 < 2)
		p3 = 2;
	p3 -= 1;

	parent->get_sounder()->set_pitch("eng2", p);
	parent->get_sounder()->set_pitch("eng1", p2);
	parent->get_sounder()->set_pitch("eng3", p3);
}

void Controler::setoff_init(HANDLE lock)
{
	WaitForSingleObject(lock, INFINITE);

	right_length = 0.;
	right_height = 0.;
	left_length = 0.;
	left_height = 0.;
	t_normal = t_pro = 0.;

	ReleaseMutex(lock);
}

int Controler::setoff(bool is_cam, OIS::Keyboard* kb, Real time, HANDLE lock)
{
	if (time > MAX_FRAME_TIME)
		time = MAX_FRAME_TIME;
	if (time < MIN_FRAME_TIME)
		time = MIN_FRAME_TIME;
	if (is_cam)
	{
		WaitForSingleObject(lock, INFINITE);
		//TODO
		
		ReleaseMutex(lock);
	}
	else
	{
		if (kb->isKeyDown(OIS::KC_DOWN))
			t_normal += time;
		else
			t_normal *= Math::Exp(-KBMODE_DEPRESS_RATE * time);

		if (kb->isKeyDown(OIS::KC_W))
			t_pro += time;
		else
			t_pro *= Math::Exp(-KBMODE_DEPRESS_RATE * time);

		float fp = (t_pro > t_normal ? t_pro : t_normal) / SETOFF_TIME + 1;
		if (fp > 2)
			fp = 2;
		parent->get_sounder()->set_pitch("eng2", fp);
		parent->get_sounder()->set_pitch("eng1", 1);
		parent->get_sounder()->set_pitch("eng3", 1);


		if (t_normal > SETOFF_TIME)
			return 2;
		if (t_pro > SETOFF_TIME)
			return 1;
	}
	return 0;
}

CvCapture* Controler::get_capture()
{
	return capture;
}